//===--- StorageImpl.h - Storage declaration access impl --------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file defines types for describing the implementation of an
// AbstractStorageDecl.
//
//===----------------------------------------------------------------------===//

#ifndef SWIFT_STORAGEIMPL_H
#define SWIFT_STORAGEIMPL_H

#include "polarphp/basic/Range.h"

namespace polar {

using llvm::StringRef;
using polar::IntRange;

enum StorageIsMutable_t : bool {
   StorageIsNotMutable = false,
   StorageIsMutable = true
};

enum class OpaqueReadOwnership : uint8_t {
   /// An opaque read produces an owned value.
   Owned,

   /// An opaque read produces a borrowed value.
   Borrowed,

   /// An opaque read can be either owned or borrowed, depending on the
   /// preference of the caller.
   OwnedOrBorrowed
};

// Note that the values of these enums line up with %select values in
// diagnostics.
enum class AccessorKind {
#define ACCESSOR(ID) ID,
#define LAST_ACCESSOR(ID) Last = ID
#include "polarphp/ast/AccessorKindsDef.h"
};

const unsigned NumAccessorKinds = unsigned(AccessorKind::Last) + 1;

static inline IntRange<AccessorKind> allAccessorKinds()
{
   return IntRange<AccessorKind>(AccessorKind(0),
                                 AccessorKind(NumAccessorKinds));
}

/// Whether an access to storage is for reading, writing, or both.
enum class AccessKind : uint8_t {
   /// The access is just to read the current value.
   Read,

   /// The access is just to overwrite the current value.
   Write,

   /// The access may require either reading or writing the current value.
   ReadWrite
};

/// Produce the aggregate access kind of the combination of two accesses.
inline AccessKind combineAccessKinds(AccessKind a, AccessKind b) {
   // If they're the same, use that; otherwise, all combinations combine
   // ReadWrite.
   return (a == b ? a : AccessKind::ReadWrite);
}

class AccessStrategy {
public:
   enum Kind : uint8_t {
      /// The declaration is a VarDecl with its own physical storage; access
      /// that storage directly.
      Storage,

      /// Directly call an accessor of some sort.  The strategy includes
      /// an accessor kind.
      DirectToAccessor,

      /// Dispatch to an accessor of some sort.  The strategy includes an
      /// accessor kind.
      DispatchToAccessor,

      /// The access is a ReadWrite access and should be implemented by
      /// separately performing a Read into a temporary variable followed by
      /// a Write access back into the storage.
      MaterializeToTemporary,
   };

private:
   Kind TheKind;
   Kind FirstKind;
   AccessorKind FirstAccessor;
   Kind SecondKind;
   AccessorKind SecondAccessor;

   AccessStrategy(Kind kind)
      : TheKind(kind) {
      assert(kind == Storage);
   }

   AccessStrategy(Kind kind, AccessorKind accessor)
      : TheKind(kind), FirstAccessor(accessor) {
      // Generally used for one of the accessor strategies, but also used
      // for constructing a first or second strategy.
   }

   AccessStrategy(AccessStrategy readStrategy, AccessStrategy writeStrategy)
      : TheKind(MaterializeToTemporary),
        FirstKind(readStrategy.TheKind),
        FirstAccessor(readStrategy.FirstAccessor),
        SecondKind(writeStrategy.TheKind),
        SecondAccessor(writeStrategy.FirstAccessor) {
      assert(readStrategy.TheKind != MaterializeToTemporary);
      assert(writeStrategy.TheKind != MaterializeToTemporary);
   }

public:
   static AccessStrategy getStorage() {
      return { Storage };
   }

   static AccessStrategy getAccessor(AccessorKind accessor, bool dispatched) {
      return { dispatched ? DispatchToAccessor : DirectToAccessor, accessor };
   }

   static AccessStrategy getMaterializeToTemporary(AccessStrategy read,
                                                   AccessStrategy write) {
      return { read, write };
   }

   Kind getKind() const { return TheKind; }

   bool hasAccessor() const {
      return TheKind == DirectToAccessor || TheKind == DispatchToAccessor;
   }

   AccessorKind getAccessor() const {
      assert(hasAccessor());
      return FirstAccessor;
   }

   AccessStrategy getReadStrategy() const {
      assert(TheKind == MaterializeToTemporary);
      return { FirstKind, FirstAccessor };
   }
   AccessStrategy getWriteStrategy() const {
      assert(TheKind == MaterializeToTemporary);
      return { SecondKind, SecondAccessor };
   }
};

/// How are read accesses implemented?
enum class ReadImplKind {
   /// There's storage.
   Stored,

   /// The superclass's read implementation is directly inherited.
   Inherited,

   /// There's a getter.
   Get,

   /// There's an immutable addressor.
   Address,

   /// There's a read coroutine.
   Read,
};
enum { NumReadImplKindBits = 4 };

StringRef getReadImplKindName(ReadImplKind kind);

/// How are simple write accesses implemented?
enum class WriteImplKind {
   /// It's immutable.
   Immutable,

   /// There's storage.
   Stored,

   /// There are observers on top of the storage.
   /// TODO: maybe add a StoredWithDidSet here and to ReadWriteImplKind?
   StoredWithObservers,

   /// There are observers on top of the superclass's write implementation.
   InheritedWithObservers,

   /// There's a setter.
   Set,

   /// There's a mutable addressor.
   MutableAddress,

   /// There's a modify coroutine.
   Modify,
};
enum { NumWriteImplKindBits = 4 };

StringRef getWriteImplKindName(WriteImplKind kind);

/// How are read-write accesses implemented?
enum class ReadWriteImplKind {
   /// It's immutable.
   Immutable,

   /// There's storage.
   Stored,

   /// There's a mutable addressor.
   MutableAddress,

   /// Do a read into a temporary and then a write back.
   MaterializeToTemporary,

   /// There's a modify coroutine.
   Modify,
};
enum { NumReadWriteImplKindBits = 4 };

StringRef getReadWriteImplKindName(ReadWriteImplKind kind);

class StorageImplInfo {
   using IntType = uint16_t;
   static_assert(NumReadImplKindBits + NumWriteImplKindBits
                 + NumReadWriteImplKindBits <= 16,
                 "bit count exceeds IntType range");
   IntType Read : NumReadImplKindBits;
   IntType Write : NumWriteImplKindBits;
   IntType ReadWrite : NumReadWriteImplKindBits;

public:
   /// A convenience constructor for building immutable storage.
   StorageImplInfo(ReadImplKind readImpl)
      : StorageImplInfo(readImpl, WriteImplKind::Immutable,
                        ReadWriteImplKind::Immutable) {}

   /// The primary constructor.
   StorageImplInfo(ReadImplKind readImpl,
                   WriteImplKind writeImpl,
                   ReadWriteImplKind readWriteImpl)
      : Read(IntType(readImpl)),
        Write(IntType(writeImpl)),
        ReadWrite(IntType(readWriteImpl)) {
#ifndef NDEBUG
      assert((writeImpl == WriteImplKind::Immutable)
             == (readWriteImpl == ReadWriteImplKind::Immutable) &&
             "write and read-write disagree about immutability");

      switch (writeImpl) {
      case WriteImplKind::Immutable:
         // No other consistency checks are required if the storage is immutable.
         return;

      case WriteImplKind::Stored:
         assert(readImpl == ReadImplKind::Stored);
         assert(readWriteImpl == ReadWriteImplKind::Stored);
         return;

      case WriteImplKind::StoredWithObservers:
         assert(readImpl == ReadImplKind::Stored);
         assert(readWriteImpl == ReadWriteImplKind::MaterializeToTemporary);
         return;

      case WriteImplKind::InheritedWithObservers:
         assert(readImpl == ReadImplKind::Inherited);
         assert(readWriteImpl == ReadWriteImplKind::MaterializeToTemporary);
         return;

      case WriteImplKind::Set:
         assert(readImpl == ReadImplKind::Get ||
                readImpl == ReadImplKind::Address ||
                readImpl == ReadImplKind::Read);
         assert(readWriteImpl == ReadWriteImplKind::MaterializeToTemporary ||
                readWriteImpl == ReadWriteImplKind::Modify);
         return;

      case WriteImplKind::Modify:
         assert(readImpl == ReadImplKind::Get ||
                readImpl == ReadImplKind::Address ||
                readImpl == ReadImplKind::Read);
         assert(readWriteImpl == ReadWriteImplKind::Modify);
         return;

      case WriteImplKind::MutableAddress:
         assert(readImpl == ReadImplKind::Get ||
                readImpl == ReadImplKind::Address ||
                readImpl == ReadImplKind::Read);
         assert(readWriteImpl == ReadWriteImplKind::MutableAddress);
         return;
      }
      llvm_unreachable("bad write impl kind");
#endif
   }

   static StorageImplInfo getSimpleStored(StorageIsMutable_t isMutable) {
      return { ReadImplKind::Stored,
               isMutable ? WriteImplKind::Stored
                         : WriteImplKind::Immutable,
               isMutable ? ReadWriteImplKind::Stored
                         : ReadWriteImplKind::Immutable };
      }

      static StorageImplInfo getOpaque(StorageIsMutable_t isMutable,
      OpaqueReadOwnership ownership) {
      return (isMutable ? getMutableOpaque(ownership)
      : getImmutableOpaque(ownership));
   }

   /// Describe the implementation of a immutable property implemented opaquely.
   static StorageImplInfo getImmutableOpaque(OpaqueReadOwnership ownership) {
      return { getOpaqueReadImpl(ownership) };
   }

   /// Describe the implementation of a mutable property implemented opaquely.
   static StorageImplInfo getMutableOpaque(OpaqueReadOwnership ownership) {
      return { getOpaqueReadImpl(ownership), WriteImplKind::Set,
               ReadWriteImplKind::Modify };
   }

   static StorageImplInfo getComputed(StorageIsMutable_t isMutable) {
      return (isMutable ? getMutableComputed()
                        : getImmutableComputed());
   }

   /// Describe the implementation of an immutable property implemented
   /// with just a getter.
   static StorageImplInfo getImmutableComputed() {
      return { ReadImplKind::Get };
   }

   /// Describe the implementation of a mutable property implemented with
   /// getter and setter.
   static StorageImplInfo getMutableComputed() {
      return { ReadImplKind::Get, WriteImplKind::Set,
               ReadWriteImplKind::MaterializeToTemporary };
   }

   /// Does this implementation description require storage?
   bool hasStorage() const {
      return getReadImpl() == ReadImplKind::Stored;
   }

   /// Does this describe a simply-stored variable?
   bool isSimpleStored() const {
      return getReadImpl() == ReadImplKind::Stored &&
            (getWriteImpl() == WriteImplKind::Stored ||
             getWriteImpl() == WriteImplKind::Immutable);
   }

   /// Does this describe storage that supports mutation?
   StorageIsMutable_t supportsMutation() const {
      return StorageIsMutable_t(getWriteImpl() != WriteImplKind::Immutable);
   }

   ReadImplKind getReadImpl() const {
      return ReadImplKind(Read);
   }
   WriteImplKind getWriteImpl() const {
      return WriteImplKind(Write);
   }
   ReadWriteImplKind getReadWriteImpl() const {
      return ReadWriteImplKind(ReadWrite);
   }

private:
   static ReadImplKind getOpaqueReadImpl(OpaqueReadOwnership ownership) {
      switch (ownership) {
      case OpaqueReadOwnership::Owned:
         return ReadImplKind::Get;
      case OpaqueReadOwnership::OwnedOrBorrowed:
      case OpaqueReadOwnership::Borrowed:
         return ReadImplKind::Read;
      }
      llvm_unreachable("bad read-ownership kind");
   }
};

StringRef getAccessorLabel(AccessorKind kind);
void simple_display(llvm::raw_ostream &out, AccessorKind kind);

} // end namespace polar

#endif
